/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.objectsearch.parsers;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import javax.xml.parsers.*;

import org.objectsearch.settings.LibSettings;
import org.objectsearch.sqlsearch.ObjectProperty;
import org.w3c.dom.*;
import org.xml.sax.*;

/**
 * it is the main engine that provides the mapping of the classes referenced in
 * the main xml configuration file and provides use of the properties in the
 * main method
 * 
 * @author Carlos
 */
@SuppressWarnings("unused")
public class ClassParserEngine {

	/**
	 * this is the w3c document structure containing the classes in the main xml
	 */
	private Document document;

	/**
	 * the main constructor of the engine wich parses an input stream containing
	 * the xml of the mapping structures in oracle
	 * 
	 * @param in
	 *            the inputstream that contains the xml of the mapping of the
	 *            database objects
	 * @throws javax.xml.parsers.ParserConfigurationException
	 *             if the parser could not be configurated successfully
	 * @throws org.xml.sax.SAXException
	 *             is thrown if the document is not well formed
	 * @throws java.io.IOException
	 *             if the document could not be found or if could not be read
	 */
	public ClassParserEngine(InputStream in)
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		document = builder.parse(in);
	}

	/**
	 * the main constructor of the engine wich parses an String containing the
	 * xml location in the filesystem
	 * 
	 * @param xmlLocation
	 *            the xml location in the filesystem
	 * @throws ParserConfigurationException
	 *             if the factorymembers could not be configurated normally
	 * @throws SAXException
	 *             if the xml could not be read normally
	 * @throws IOException
	 *             if an error occurs reading the xml
	 */
	public ClassParserEngine(String xmlLocation)
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		document = builder.parse(xmlLocation);
	}

	/**
	 * the constructor of the parser engine using a org.w3c.document.Document
	 * containing the structures of the mapped objects in the database
	 * 
	 * @param document
	 *            document contaning the structures of the mapped
	 */
	public ClassParserEngine(org.w3c.dom.Document document) {
		this.document = document;
	}

	/**
	 * gets the current Document Model for the parsing engine
	 * 
	 * @return the current document used for parsing
	 */
	public Document getDocument() {
		return document;
	}

	/**
	 * sets the current document model for the parsing engine
	 * 
	 * @param document
	 *            the current document wich will be used for parsing the classes
	 */
	public void setDocument(Document document) {
		this.document = document;
	}

	/**
	 * get the mappings of the object in a match of database and value of the
	 * object
	 * 
	 * @param object
	 *            the object that is mapped to the database
	 * @param fieldType
	 *            describes if the fields contained are keys or values
	 * @return the hashtable that contains the map of keys and values of the
	 *         database
	 * @throws java.lang.Exception
	 *             if the method or property could not be read successfully
	 */
	@SuppressWarnings("unchecked")
	private Hashtable getObjectHashValues(Object object, String fieldType)
			throws Exception {
		Hashtable hash = new Hashtable();
		String classForName = object.getClass().getName();
		Element element = getClassElement(classForName);
		if (LibSettings.DEBUG) {
			System.out.println(element);
		}
		NodeList list = element.getElementsByTagName(fieldType);
		int count0 = list.getLength();
		for (int j0 = 0; j0 < count0; j0++) {
			Element keysElement = (Element) list.item(j0);
			NodeList keysElements = keysElement.getElementsByTagName("field");
			int count1 = keysElements.getLength();
			for (int i = 0; i < count1; i++) {
				Node node = keysElements.item(i);
				NamedNodeMap attributes = node.getAttributes();
				String objectProperty = attributes.getNamedItem("name")
						.getNodeValue();
				String databaseField = attributes.getNamedItem("dbequivalent")
						.getNodeValue();
				Object property = this.pullObjectFromProperty(object,
						objectProperty);
				if (LibSettings.DEBUG) {
					System.out.println("campo: " + databaseField
							+ " propiedad: " + property);
				}
				if (property != null) {
					hash.put(databaseField, property);
				}
			}
		}
		return hash;
	}

	/**
	 * get the the full object hashvalues containing all the fields and
	 * properties in the object
	 * 
	 * @param object
	 *            the object mapped to the database
	 * @return the hashtable containing the properties of the mapping of fields
	 *         and properties in the object
	 * @throws java.lang.Exception
	 *             if the properties or methods could not be accessed
	 */
	@SuppressWarnings("unchecked")
	public Hashtable getObjectHashValues(Object object) throws Exception {
		Hashtable hash = new Hashtable();
		String classForName = object.getClass().getName();
		Element element = getClassElement(classForName);
		if (LibSettings.DEBUG) {
			System.out.println(element);
		}
		NodeList keysElements = element.getElementsByTagName("field");
		int count1 = keysElements.getLength();
		for (int i = 0; i < count1; i++) {
			Node node = keysElements.item(i);
			NamedNodeMap attributes = node.getAttributes();
			String objectProperty = attributes.getNamedItem("name")
					.getNodeValue();
			String databaseField = attributes.getNamedItem("dbequivalent")
					.getNodeValue();
			Object property = this.pullObjectFromProperty(object,
					objectProperty);
			if (LibSettings.DEBUG) {
				System.out.println("campo: " + databaseField + " propiedad: "
						+ property);
			}
			if (property != null) {
				hash.put(databaseField, property);
			}
		}
		return hash;
	}

	/**
	 * get the fieldNames of the object
	 * 
	 * @param object
	 *            the object containing the fields in the main
	 * @return an array of strings containing the list of fields that the
	 *         database will retrieve in the database query
	 */
	public String[] getFieldNames(Object object) {
		String[] fieldNames = null;
		String classForName = object.getClass().getName();
		Element element = getClassElement(classForName);
		if (LibSettings.DEBUG) {
			System.out.println(element);
		}
		NodeList keysElements = element.getElementsByTagName("field");
		int count1 = keysElements.getLength();
		fieldNames = new String[count1];
		for (int i = 0; i < count1; i++) {
			Node node = keysElements.item(i);
			NamedNodeMap attributes = node.getAttributes();
			String databaseField = attributes.getNamedItem("dbequivalent")
					.getNodeValue();
			fieldNames[i] = databaseField;
		}
		return fieldNames;

	}

	/**
	 * get the mapped keys in the main object
	 * 
	 * @param object
	 *            the object mapped in the database
	 * @return the hashtable containing the mapping of keys and fields in the
	 *         database
	 * @throws java.lang.Exception
	 */
	@SuppressWarnings("unchecked")
	public Hashtable getKeys(Object object) throws Exception {
		return getObjectHashValues(object, "primary-key");
	}

	/**
	 * get the mapped fields in the main object
	 * 
	 * @param object
	 *            the object mapped in the database
	 * @return the hashtable containing the mapping of keys and fields in the
	 *         database
	 * @throws java.lang.Exception
	 */
	@SuppressWarnings("unchecked")
	public Hashtable getFields(Object object) throws Exception {
		return getObjectHashValues(object, "fields");
	}

	/**
	 * get the objectproperties of the mapped object, contains the relation
	 * between fields, properties and types in the database
	 * 
	 * @param object
	 *            the object in the database context
	 * @return the hashtable containing the properties of the object
	 */
	@SuppressWarnings("unchecked")
	public Hashtable getProperties(Object object) {
		Hashtable hash = new Hashtable();
		String classForName = object.getClass().getName();
		Element element = getClassElement(classForName);
		NodeList elementsByTagName = element.getElementsByTagName("field");
		int count0 = elementsByTagName.getLength();
		for (int i = 0; i < count0; i++) {
			Element node = (Element) elementsByTagName.item(i);
			String name = node.getAttribute("name");
			String type = node.getAttribute("type");
			String dbequivalent = node.getAttribute("dbequivalent");
			if (LibSettings.DEBUG) {
				System.out.println("name:" + name + " type: " + type
						+ " equivalent:" + dbequivalent);
			}
			ObjectProperty prop = new ObjectProperty();
			prop.setName(name);
			prop.setType(type);
			prop.setDbEquivalent(dbequivalent);
			hash.put(dbequivalent, prop);
		}
		return hash;
	}

	/**
	 * gets the table name witch stores the objects in the database
	 * 
	 * @param object
	 *            the object mapped in the database
	 * @return the name of the table in the schema containing the objects
	 */
	public String getTableName(Object object) {
		String className = object.getClass().getName();
		Element el = this.getClassElement(className);
		if (el == null) {
			throw new IllegalArgumentException(
					"Tables are not set in the Mapping file for the object: "
							+ className);
		}
		String tableName = el.getAttribute("table-name");
		return tableName;
	}

	/**
	 * gets the w3c element containing the database mapping
	 * 
	 * @param className
	 *            the classforname in the schema
	 * @return the w3c element wich contains the database mapping the object
	 */
	public Element getClassElement(String className) {
		NodeList list = document.getElementsByTagName("class");
		int lentgh = list.getLength();
		for (int i = 0; i < lentgh; i++) {
			Element element = (Element) list.item(i);
			String id = element.getAttribute("id");
			if (id.equalsIgnoreCase(className)) {
				return element;
			}
		}
		return null;
	}

	/**
	 * gets the object wich is contained in the property
	 * 
	 * @param o
	 *            the object wich contains the property
	 * @param property
	 *            the property contained in the object wich is called to pull
	 *            the properties contained
	 * @return the object contained by in the property of the object
	 * @throws java.lang.Exception
	 *             occurs if the method could not be read correctly
	 */

	@SuppressWarnings("unchecked")
	/*
	 * public Object pullObjectFromProperty(Object o, String property) throws
	 * Exception { Class _class = o.getClass(); Field field =
	 * this.getFieldFromClass(_class, property); Object result = null; if
	 * (!field.isAccessible()) { field.setAccessible(true); result =
	 * field.get(o); field.setAccessible(false); } else { result = field.get(o);
	 * } if (LibSettings.DEBUG) { System.out.println(property); } return result;
	 * }
	 */
	public Object pullObjectFromProperty(Object o, String property)
			throws Exception {
		Class _class = o.getClass();
		char first = property.charAt(0);
		char upperFirst = Character.toUpperCase(first);
		property = property.replaceFirst(String.valueOf(first),
				String.valueOf(upperFirst));
		property = "get" + property;
		Class[] args = null;
		Method method = _class.getMethod(property, args);
		Object[] varArgs = null;
		Object result = method.invoke(o, varArgs);
		return result;
	}

	/**
	 * 
	 * @param _class
	 * @param property
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Field getFieldFromClass(Class _class, String property) {
		Field field = null;
		try {
			Class superClass = _class.getSuperclass();
			if (superClass != null) {
				try {
					superClass.getDeclaredField(property);
				} catch (Exception ex) {
					field = _class.getDeclaredField(property);
				}
			} else {
				field = _class.getDeclaredField(property);
			}
		} catch (Exception e) {
		}
		if (field == null) {
			throw new IllegalArgumentException("the property: " + property
					+ " in the class: " + _class.getName()
					+ " does not exist! ");
		}
		return field;
	}

	/**
	 * set the value of the property of the object with an object
	 * 
	 * @param o
	 *            the object that is going to be setted the property with a
	 *            value
	 * @param field
	 *            it contains the object that is going to be assigned to the
	 *            property
	 * @param property
	 *            the property in the object
	 * @return an object result of the ivocation
	 * @throws java.lang.Exception
	 *             is thrown if the method could not be accessed normally
	 */
	@SuppressWarnings("unchecked")
	/*
	 * public Object setObjectFromProperty(Object o, Object field, String
	 * property) throws Exception { Class _class = o.getClass(); Field
	 * objectField = this.getFieldFromClass(_class, property); if
	 * (!objectField.isAccessible()) { objectField.setAccessible(true); if
	 * (objectField.getType().getName().equals("java.lang.Float") &&
	 * field.getClass().getName().equals("java.lang.Integer")) { if (field !=
	 * null) { Float f = new Float(field.toString()); objectField.set(o, f); } }
	 * else { objectField.set(o, field); }
	 * 
	 * objectField.setAccessible(false); } else { objectField.set(o, field); }
	 * return field; }
	 */
	public Object setObjectFromProperty(Object o, Object field, String property)
			throws Exception {
		Class _class = o.getClass();
		char first = property.charAt(0);
		char upperFirst = Character.toUpperCase(first);
		property = property.replaceFirst(String.valueOf(first),
				String.valueOf(upperFirst));
		property = "set" + property;
		Class fieldClass = field.getClass();
		String fieldClassName = fieldClass.getName();
		Object result = null;
		if (fieldClassName.equals("java.lang.Integer")) {
			Class[] fieldNumerics = new Class[3];
			fieldNumerics[0] = Class.forName("java.lang.Float");
			fieldNumerics[1] = Class.forName("java.lang.Double");
			fieldNumerics[2] = Class.forName("java.lang.Integer");
			Method method = null;
			boolean success = false;
			for (int i = 0; i < fieldNumerics.length; i++) {
				try {
					method = _class.getMethod(property, fieldNumerics[i]);
					success = true;
					if (i == 0) {
						Float f = new Float(field.toString());
						result = method.invoke(o, f);
					} else if (i == 1) {
						Double d = new Double(field.toString());
						result = method.invoke(o, d);
					} else {
						result = method.invoke(o, field);
					}
				} catch (Exception ex) {
				}
			}
			if (!success) {
				throw new IllegalArgumentException("the field: " + property
						+ "in the class: " + o.getClass().getName()
						+ " does not exist");
			}

		} else if (fieldClassName.equals("java.sql.Timestamp")) {
			java.sql.Timestamp timestamp = (java.sql.Timestamp) field;
			Date date = new Date(timestamp.getTime());
			// Class dateClass = Class.forName( "java.util.Date" );
			Method method = _class.getMethod(property, date.getClass());
			result = method.invoke(o, date);
		} else if (fieldClassName.equals("java.sql.Date")) {
			Date date = new Date(((java.sql.Date) field).getTime());
			Method method = _class.getMethod(property, date.getClass());
			result = method.invoke(o, date);
		} else {
			Method method = _class.getMethod(property, fieldClass);
			result = method.invoke(o, field);
		}
		return result;
	}

	/**
	 * replace the objectOrders by the real ordersby in the xml definition
	 * schema and prepares them for use in a sql created for use in the selected
	 * database
	 * 
	 * @param object
	 *            the current object that contains the fields mapped to the
	 *            database schema
	 * @param orders
	 *            the current orders given in the object wich are driven
	 *            selected object that contains the mapped names wich are a
	 *            pseudo order by, in the selected mask
	 * @return the real ordersBy used in the selected name
	 */
	@SuppressWarnings("unchecked")
	public String[] getRealOrdersBy(Object object, String[] orders) {
		String[] realOrdersBy = new String[orders.length];
		Class _class = object.getClass();
		String classForName = _class.getName();
		Element element = getClassElement(classForName);
		if (element != null) {
			Properties props = new Properties();
			NodeList nodeList = element.getElementsByTagName("field");
			int nodeLength = nodeList.getLength();
			for (int i = 0; i < nodeLength; i++) {
				Element elemento = (Element) nodeList.item(i);
				String dbEquivalent = elemento.getAttribute("dbequivalent");
				String name = elemento.getAttribute("name");
				props.setProperty(name, dbEquivalent);
			}
			for (int i = 0; i < orders.length; i++) {
				String order = orders[i];
				int indice = order.indexOf(" ");
				String orderIndex = "";
				String first = order;
				if (indice != -1) {
					first = order.substring(0, indice);
					System.out.println("primero: " + first);
					orderIndex = order.substring(indice, order.length());
					orderIndex = orderIndex.replaceAll("\"", "");
					orderIndex = orderIndex.replaceAll("'", "");
				}
				String realOrder = props.getProperty(first);
				realOrdersBy[i] = realOrder + orderIndex;
			}
		}
		return realOrdersBy;
	}

}
